package ooo.reindeer.cedf.components.flow;

import io.netty.util.internal.ObjectUtil;
import ooo.reindeer.cedf.Event;
import ooo.reindeer.cedf.components.handler.IErrorHandler;
import ooo.reindeer.commons.Property;
import ooo.reindeer.logging.ILogger;
import ooo.reindeer.logging.LogUtil;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class Flow {

    private static final ILogger logger = LogUtil.getLogger(Flow.class);
    private static final ILogger redeoLogger = LogUtil.getLogger("Redo");


    private static final String PROPERTY_STEP = "step";
    private static final String PROPERTY_RELATION = "relation";
    private static final String PROPERTY_ERROR = "error";

    private String id;

    private final Map<String, Step> step_map = new ConcurrentHashMap<String, Step>();
    private final Map<String, Step> relation_map = new ConcurrentHashMap<String, Step>();
    private final IErrorHandler error_handler = new FlowErrorHandler();

    public boolean init(String id, Property config) {
        logger.info("init:id=[{}]	config=[{}]", id,config);
        this.id = id;
        Property step_properties = config.getProperty(PROPERTY_STEP);
        for (String key : step_properties.getKeySet()) {
            Property step_property = step_properties.getProperty(key);
            Step step = new Step();
            step.init(key, step_property);
            step_map.put(key, step);
        }
        Property relation_properties = config.getProperty(PROPERTY_RELATION);
        for (String key : relation_properties.getKeySet()) {
            Property relation_property = relation_properties.getProperty(key);
            String[] rlist = relation_property.value("").split(",");
            if (rlist.length != 3) {
                continue;
            }
            if(rlist[0].isEmpty()){
                logger.info("init:id=[{}]	relation=        BEGIN-->{}", id,rlist[2]);
            }else if(rlist[1].isEmpty()){
                logger.info("init:id=[{}]	relation=        {}-->{}", id, rlist[0], rlist[2]);
            }else {
                logger.info("init:id=[{}]	relation=        {}-->|{}|{}", id, rlist[0], rlist[1], rlist[2]);
            }
            String rkey = makeKey(rlist[0], rlist[1]);
            relation_map.put(rkey, step_map.get(rlist[2]));
        }

        error_handler.init(id,config.getProperty(PROPERTY_ERROR));

        return true;
    }

    public Object handle(Event event) {
        FlowResult result = new FlowResult(event.getId(), id);
        try {
            Step step = findNextStep("", "");
            Object data = event.getData();

            String ret;
            redeoLogger.info("{\"flow\":\"{}\",\"begin\":\"{}\",\"event\":{}}", id, step.getId(), event);
            try {
                while (step != null) {
                    result.addStep(step);
                    ret = step.exec(data);
                    if (logger.isDebugEnabled()) {
                        logger.debug("flow=[{}]  step=[{}]   event=[{}]", id, step.getId(), event);
                    }
                    if (ret == null) ret = "";
                    result.addResult(step, ret);
                    step = findNextStep(step.getId(), ret);
                }
            } catch (Throwable t) {
                result.addResult(step, t.getMessage());
                error_handler.onError(step.getId(), event, t);
            }
            redeoLogger.info("{\"flow\":\"{}\",\"event\":{},\"result\":{}}", id, event, result);

        }catch (Throwable t){
            t.printStackTrace();
        }
        return result;
    }

    private Step findNextStep(String id, String condition) {
        Step step = relation_map.get(makeKey(id, condition));
        if (step != null) {
            return step;
        }

        step = relation_map.get(makeKey(id, ""));

        if(step !=null){
            logger.warn("unknown condition step=[{}] condition=[{}] use unconditional step {}", id, condition,step);
        }

        return step;
    }

    private String makeKey(String id, String condition) {
        return  id + ":" +
                condition;
    }

    public Flow(){
        logger.trace("Flow()");
    }

}
